/**
 * Copyright 2017 Bartosz Schiller
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ashlikun.pdfview

import android.graphics.Bitmap
import android.graphics.RectF
import android.util.Size
import android.util.SizeF
import android.util.SparseBooleanArray
import com.ashlikun.pdfview.model.PdfBookmark
import com.ashlikun.pdfview.model.PdfLink
import com.ashlikun.pdfview.model.PdfMeta
import com.ashlikun.pdfview.exception.PageRenderingException
import com.ashlikun.pdfview.renderer.IPdfRenderer
import com.ashlikun.pdfview.renderer.PdfSystemRenderer
import com.ashlikun.pdfview.util.PageSizeCalculator

internal class PdfFile(
    private val pdfRenderer: IPdfRenderer?,
    val viewSize: Size,
    val config: PDFViewConfig,
) {
    var pagesCount = 0
        private set

    /**
     * 原始页面大小
     */
    private val originalPageSizes: MutableList<Size> = ArrayList()

    /**
     * 缩放页面大小
     */
    private val pageSizes: MutableList<SizeF> = ArrayList()

    /**
     * 打开的页面带有打开是否成功的指示器
     */
    private val openedPages = SparseBooleanArray()

    /**
     * 具有最大宽度的页面
     */
    private var originalMaxWidthPageSize = Size(0, 0)

    /**
     * 具有最大高度的页面
     */
    private var originalMaxHeightPageSize = Size(0, 0)

    /**
     * 具有最大高度的缩放页面
     */
    private var maxHeightPageSize = SizeF(0f, 0f)

    /**
     * 具有最大宽度的缩放页面
     */
    private var maxWidthPageSize = SizeF(0f, 0f)

    /**
     * 计算的页面偏移量
     */
    private val pageOffsets: MutableList<Float> = ArrayList()

    /**
     * 计算的页面自动间距
     */
    private val pageSpacing: MutableList<Float> = ArrayList()

    /**
     * 计算的文档长度（宽度或高度，取决于滑动模式）
     */
    private var documentLength = 0f

    init {
        setup(viewSize)
    }

    private fun setup(viewSize: Size) {
        pagesCount = if (config.userPages != null) {
            config.userPages!!.size
        } else {
            pdfRenderer!!.pageCount
        }
        for (i in 0 until pagesCount) {
            val pageSize = pdfRenderer!!.getPageSize(documentPage(i))
            if (pageSize.width > originalMaxWidthPageSize.width) {
                originalMaxWidthPageSize = pageSize
            }
            if (pageSize.height > originalMaxHeightPageSize.height) {
                originalMaxHeightPageSize = pageSize
            }
            originalPageSizes.add(pageSize)
        }
        recalculatePageSizes(viewSize)
    }

    /**
     * 视图大小更改后调用以重新计算页面大小、偏移量和文档长度
     *
     * @param viewSize 更改视图的新大小
     */
    fun recalculatePageSizes(viewSize: Size) {
        pageSizes.clear()
        val calculator = PageSizeCalculator(
            config, originalMaxWidthPageSize,
            originalMaxHeightPageSize, viewSize
        )
        maxWidthPageSize = calculator.optimalMaxWidthPageSize
        maxHeightPageSize = calculator.optimalMaxHeightPageSize
        for (size in originalPageSizes) {
            pageSizes.add(calculator.calculate(size))
        }
        if (config.isAutoSpacing) {
            prepareAutoSpacing(viewSize)
        }
        prepareDocLen()
        preparePagesOffset()
    }

    fun getPageSize(pageIndex: Int): SizeF {
        val docPage = documentPage(pageIndex)
        return if (docPage < 0) {
            SizeF(0f, 0f)
        } else pageSizes[pageIndex]
    }

    fun getScaledPageSize(pageIndex: Int, zoom: Float): SizeF {
        val size = getPageSize(pageIndex)
        return SizeF(size.width * zoom, size.height * zoom)
    }

    val maxPageSize: SizeF
        /**
         * 获取具有最大尺寸的页面大小（垂直模式下的宽度和水平模式下的高度）
         */
        get() = if (config.isVertical) maxWidthPageSize else maxHeightPageSize
    val maxPageWidth: Float
        get() = maxPageSize.width
    val maxPageHeight: Float
        get() = maxPageSize.height

    private fun prepareAutoSpacing(viewSize: Size) {
        pageSpacing.clear()
        for (i in 0 until pagesCount) {
            val pageSize = pageSizes[i]
            var spacing = Math.max(0f, if (config.isSwipeEnabled) viewSize.height - pageSize.height else viewSize.width - pageSize.width)
            if (i < pagesCount - 1) {
                spacing += config.spacing.toFloat()
            }
            pageSpacing.add(spacing)
        }
    }

    private fun prepareDocLen() {
        var length = 0f
        for (i in 0 until pagesCount) {
            val pageSize = pageSizes[i]
            length += if (config.isVertical) pageSize.height else pageSize.width
            if (config.isAutoSpacing) {
                length += pageSpacing[i]
            } else if (i < pagesCount - 1) {
                length += config.spacing.toFloat()
            }
        }
        documentLength = length
    }

    private fun preparePagesOffset() {
        pageOffsets.clear()
        var offset = 0f
        for (i in 0 until pagesCount) {
            val pageSize = pageSizes[i]
            val size = if (config.isVertical) pageSize.height else pageSize.width
            if (config.isAutoSpacing) {
                offset += pageSpacing[i] / 2f
                if (i == 0) {
                    offset -= config.spacing / 2f
                } else if (i == pagesCount - 1) {
                    offset += config.spacing / 2f
                }
                pageOffsets.add(offset)
                offset += size + pageSpacing[i] / 2f
            } else {
                pageOffsets.add(offset)
                offset += size + config.spacing
            }
        }
    }

    fun getDocLen(zoom: Float): Float {
        return documentLength * zoom
    }

    /**
     * 如果垂直滑动，则获取页面的高度，如果水平滑动，则获得页面的宽度。
     */
    fun getPageLength(pageIndex: Int, zoom: Float): Float {
        val size = getPageSize(pageIndex)
        return (if (config.isVertical) size.height else size.width) * zoom
    }

    fun getPageSpacing(pageIndex: Int, zoom: Float): Float {
        val spacing = if (config.isAutoSpacing) pageSpacing[pageIndex] else config.spacing.toFloat()
        return spacing * zoom
    }

    /**
     * 获取主页面偏移量，即Y表示垂直滚动，X表示水平滚动
     */
    fun getPageOffset(pageIndex: Int, zoom: Float): Float {
        val docPage = documentPage(pageIndex)
        return if (docPage < 0) 0f else pageOffsets[pageIndex] * zoom
    }

    /**
     * 获取辅助页面偏移量，即X表示垂直滚动，Y表示水平滚动
     */
    fun getSecondaryPageOffset(pageIndex: Int, zoom: Float): Float {
        val pageSize = getPageSize(pageIndex)
        return if (config.isVertical) {
            val maxWidth = maxPageWidth
            zoom * (maxWidth - pageSize.width) / 2 //x
        } else {
            val maxHeight = maxPageHeight
            zoom * (maxHeight - pageSize.height) / 2 //y
        }
    }

    /**
     * 这个偏移量是哪个页面
     */
    fun getPageAtOffset(offset: Float, zoom: Float): Int {
        var currentPage = 0
        for (i in 0 until pagesCount) {
            val off = pageOffsets[i] * zoom - getPageSpacing(i, zoom) / 2f
            if (off >= offset) {
                break
            }
            currentPage++
        }
        return if (--currentPage >= 0) currentPage else 0
    }

    @Throws(PageRenderingException::class)
    fun openPage(pageIndex: Int): Boolean {
        val docPage = documentPage(pageIndex)
        if (docPage < 0) {
            return false
        }
        synchronized(lock) {
            return if (openedPages.indexOfKey(docPage) < 0) {
                try {
                    pdfRenderer!!.openPage(docPage)
                    openedPages.put(docPage, true)
                    true
                } catch (e: Exception) {
                    openedPages.put(docPage, false)
                    throw PageRenderingException(pageIndex, e)
                }
            } else false
        }
    }

    fun pageHasError(pageIndex: Int): Boolean {
        val docPage = documentPage(pageIndex)
        return !openedPages[docPage, false]
    }

    fun renderPageBitmap(bitmap: Bitmap, pageIndex: Int, width: Int, height: Int, annotationRendering: Boolean) {
        val docPage = documentPage(pageIndex)
        pdfRenderer?.renderPage(bitmap, docPage, width, height, annotationRendering)
    }

    val metaData: PdfMeta?
        get() = pdfRenderer?.metaData
    val bookmarks: List<PdfBookmark>
        get() = pdfRenderer?.bookmarks ?: ArrayList()

    fun getPageLinks(pageIndex: Int): List<PdfLink> {
        val docPage = documentPage(pageIndex)
        return pdfRenderer!!.getPageLinks(docPage)
    }

    fun mapRectToDevice(pageIndex: Int, startX: Int, startY: Int, sizeX: Int, sizeY: Int, rect: RectF): RectF {
        val docPage = documentPage(pageIndex)
        return pdfRenderer!!.mapRectToDevice(docPage, startX, startY, sizeX, sizeY, 0, rect)
    }

    fun dispose() {
        pdfRenderer?.close()
    }

    /**
     * 给定UserPage编号，此方法会对其进行限制
     * 以确保它是一个现有的页面。它会照顾到
     * 使用用户定义的页面（如果有的话）。
     *
     * @param userPage 页码。
     * @return 受限制的有效页码（例如：-2=>0）
     */
    fun determineValidPageNumberFrom(userPage: Int): Int {
        if (userPage <= 0) {
            return 0
        }
        if (config.userPages != null) {
            if (userPage >= config.userPages!!.size) {
                return config.userPages!!.size - 1
            }
        } else {
            if (userPage >= pagesCount) {
                return pagesCount - 1
            }
        }
        return userPage
    }

    fun documentPage(userPage: Int): Int {
        var documentPage = userPage
        if (config.userPages != null) {
            documentPage = if (userPage < 0 || userPage >= config.userPages!!.size) {
                return -1
            } else {
                config.userPages!![userPage]
            }
        }
        return if (documentPage < 0 || userPage >= pagesCount) {
            -1
        } else documentPage
    }

    val isSystemRenderer: Boolean
        get() = pdfRenderer is PdfSystemRenderer

    companion object {
        private val lock = Any()
    }
}
